package com.sbtr.common.calculate;
import com.sbtr.common.enums.CalculateEnum;
import com.sbtr.common.interfaces.Calculate;
import com.sbtr.util.ArrayHelper;
import org.junit.Test;

import java.io.*;
import java.util.*;

/**
 * @Author maojianping
 * @Date 2023/3/24 16:13
 * @Version 1.0
 * 用途：数据探索常用工具类
 * 1.集中趋势分析模块包含：最大值、最小值、平均值、中位数、众数
 * 2.离中趋势分析模块：极差、方差和标准差
 * 3.数据分布关系模块：峰度、偏度
 * */
public class DataExplorationUtils {

    /**
     * 1.集中趋势分析模块-----------------start-------------------------------
     * 主要包括"众数、中位数、均值"等信息
     */
    /**
     * 获取一组数据的最大值
     * @param data 数据数组
     * @return 最大值
     */
    @Calculate(CalculateEnum.getMax)
    public static <T extends Comparable<T>> T getMax(T[] data) {
        if (data == null || data.length == 0) {
            return null;
        }
        T max = data[0];
        for (int i = 1; i < data.length; i++) {
            if (DataFilterUtils.isGreaterThan(data[i], max)) {
                max = data[i];
            }
        }
        return max;
    }

    /**
     * 获取一组数据的最小值
     * @param data 数据数组
     * @return 最小值
     */
    @Calculate(CalculateEnum.getMin)
    public static <T extends Comparable<T>> T getMin(T[] data) {
        if (data == null || data.length == 0) {
            return null;
        }
        T min = data[0];
        for (int i = 1; i < data.length; i++) {
            if (DataFilterUtils.isLessThan(data[i], min)) {
                min = data[i];
            }
        }
        return min;
    }

    /**
     * 获取一组数据的平均值
     * @param data 数据数组
     * @return 平均值
     */
    @Calculate(CalculateEnum.getAverage)
    public static double getAverage(Number[] data) {
        if (data == null || data.length == 0) {
            return 0.0;
        }
        double mean = 0.0;
        for (Number num : data) {
            mean += num.doubleValue();
        }
        return mean / data.length;
    }

    /**
     * 获取一组数据的中位数
     * 解释：中位数是一组数据中居于中间位置的数值，即将所有数值按照大小顺序排列，位于中间的那个数。
     * 如果数据的个数为偶数，则中位数为中间两个数的平均值。中位数是一种比平均数更为稳健的统计量，不受极端值的影响。
     * @param data 数据数组
     * @return 中位数
     */
    @Calculate(CalculateEnum.getMedian)
    public static <T extends Number & Comparable<T>> double getMedian(T[] data) {
        if (data == null || data.length == 0) {
            return 0.0;
        }
        Arrays.sort(data);
        int len = data.length;
        if (len % 2 == 0) {
            return (data[len / 2 - 1].doubleValue() + data[len / 2].doubleValue()) / 2;
        } else {
            return data[len / 2].doubleValue();
        }
    }

    /**
     * 获取一组数据的众数
     * 解释：众数是指在一组数据中出现次数最多的数值
     * @param data 数据数组
     * @return 众数
     */
    @Calculate(CalculateEnum.getMode)
    public static <T extends Number & Comparable<T>> T getMode(T[] data) {
        if (data == null || data.length == 0) {
            return null;
        }
        Map<T, Integer> map = new HashMap<>();
        for (T num : data) {
            if (map.containsKey(num)) {
                map.put(num, map.get(num) + 1);
            } else {
                map.put(num, 1);
            }
        }
        int maxCount = 0;
        T mode = null;
        for (Map.Entry<T, Integer> entry : map.entrySet()) {
            if (entry.getValue() > maxCount) {
                maxCount = entry.getValue();
                mode = entry.getKey();
            }
        }
        return mode;
    }

    /**
     * 1.测试 最大值、最小值、平均值、中位数、众数等获取
     */
    @Test
    public void testCentralizedAnalysis() {
        // 测试 getMax 方法
        String[] strArr = {"1", "2", "7233", "551", "66"};
        Integer[] intArr  = ArrayHelper.convertStrArrToNumberArr(strArr, Integer.class);
        System.out.println("最大值为：" + getMax(intArr));

        // 测试 getMin 方法
        Double[] doubleArr = {33.1, 555.1, 113.3, 22222D, 1111111D};
        System.out.println("最小值为2：" + getMax(doubleArr));
        System.out.println("最小值为：" + getMin(doubleArr));

        // 测试 getAverage 方法
        Double[] doubleArr2 = {0.1, 0.2, 0.3, 0.4, 0.5};
        System.out.println("平均值为：" + getAverage(doubleArr2));

        // 测试 getMedian 方法
        Integer[] intArr3 = {11, 22, 33, 44, 55};
        System.out.println("中位数为：" + getMedian(intArr3));

        // 测试 getMode 方法
        Integer[] intArr4 = {1, 2, 3, 4, 5, 5};
        System.out.println("众数为：" + getMode(intArr4));
    }

    /**
     * 2.离中趋势分析模块-----------------start-------------------------------
     * 主要包括极差、方差和标准差等信息
     */
    /**
     * 计算一组数据的极差
     * 解释：极差计算公式：极差 = 最大值 - 最小值
     * @param data 数据数组
     * @return 极差
     */
    @Calculate(CalculateEnum.range)
    public static double range(Double[] data) {
        // 初始化最大、最小值为数组第一个元素
        double max = data[0];
        double min = data[0];
        // 从第二个元素遍历数组比较
        for (int i = 1; i < data.length; i++) {
            if (data[i] > max) { // 如果当前元素大于最大值，则更新最大值
                max = data[i];
            }
            if (data[i] < min) { // 如果当前元素小于最小值，则更新最小值
                min = data[i];
            }
        }
        return max - min; // 返回最大值与最小值之差，即为极差
    }

    /**
     * 计算一组数据的方差
     * @param data 数据数组
     * @return 方差
     */
    @Calculate(CalculateEnum.variance)
    public static double variance(Double[] data) {
        double stdDev = 0.0;
        int length = data.length;
        double avg = getAverage(data);
        for (int i = 0; i < length; i++) {
            stdDev += Math.pow(data[i] - avg, 2);
        }
        return stdDev / (length - 1); // 数据数量减一作为分母，能更好地反映样本方差和标准差与总体方差和标准差之间的关系。
    }

    /**
     * 计算一组数据的标准差
     * @param data 数据数组
     * @return 标准差
     */
    @Calculate(CalculateEnum.standardDeviation)
    public static double standardDeviation(Double[] data) {

        return Math.sqrt(variance(data));
    }


    /**
     * 计算一组数据的统计信息
     * @param data 数据数组
     * @return 统计信息数组，包括极差、方差、标准差、平均值
     */
    @Calculate(CalculateEnum.calculateStatistics)
    public static Double[] calculateStatistics(Double[] data) {
        Double[] statistics = new Double[4];
        statistics[0] = range(data);
        statistics[1] = variance(data);
        statistics[2] = standardDeviation(data);
        statistics[3] = getAverage(data);
        return statistics;
    }

    /**
     * 2.离中趋势分析代码测试案例
     */
    @Test
    public void TestCentripetalAnalysis() {
        Double[] data = {1.0, 2.0, 3.0, 4.0, 5.0};
        System.out.println("数据：" + Arrays.toString(data));
        System.out.println("极差：" + range(data)); // 结果：4.0
        System.out.println("方差：" + variance(data));// 结果：2.5
        System.out.println("标准差：" + standardDeviation(data));// 结果：1.5811388300841898
        System.out.println("平均值：" + getAverage(data));// 结果：3.0
        System.out.println("统计信息：" + Arrays.toString(calculateStatistics(data)));
    }
     // 2.离中趋势分析模块-----------------end-------------------------------

    /**
     * 3.数据分布关系模块-----------------start-------------------------------
     * 计算一组数据的峰度
     * @param data 数据数组
     * @return 偏度
     */
    @Calculate(CalculateEnum.kurtosis)
    public static double kurtosis(Double[] data) {
        double n = data.length;
        double mean = getAverage(data);
        double sum4 = 0.0;
        double sum2 = 0.0;
        for (double d : data) {
            sum4 += Math.pow(d - mean, 4);
            sum2 += Math.pow(d - mean, 2);
        }
        return (n * sum4) / (Math.pow(sum2, 2)) - 3;
    }

    /**
     * 计算一组数据的偏度：偏度 = (3 * (平均数 - 中位数)) / 标准差
     * @param data 数据数组
     * @return 偏度值
     */
    @Calculate(CalculateEnum.skewness)
    public static double skewness(Double[] data) {
        double mean = 0;
        for (double d : data) {
            mean += d;
        }
        mean /= data.length;
        double std = 0;
        for (double d : data) {
            std += Math.pow(d - mean, 2);
        }
        std = Math.sqrt(std / data.length);
        double skewness = 0;
        for (double d : data) {
            skewness += Math.pow(d - mean, 3);
        }
        skewness /= data.length;
        skewness /= Math.pow(std, 3);
        return skewness;
    }

    /**
     * 2.离中趋势分析代码测试案例
     */
    @Test
    public void TestDataDistribution() {
        Double[] data = {1.0, 2.0, 3.0, 4.0, 5.0};
        System.out.println("数据偏度为：" + skewness(data)); // ：0.0
        System.out.println("数据峰度为：" + kurtosis(data)); // -1.3
    }
    // 3.数据分布关系模块-----------------end-------------------------------

    /**
     * 读取txt文件内容，按列存储到同一个集合中
     * @param filePath 文件路径
     * @return 存储数据的集合
     */
    /**
     * 读取txt文件内容，按列存储到同一个集合中
     * @param filePath 文件路径
     * @return 存储数据的集合
     */
    public static List<List<Double>> readTxtFile(String filePath) {
        List<List<Double>> result = new ArrayList<>();
        try {
            BufferedReader br = new BufferedReader(new FileReader(filePath));
            String line;
            while ((line = br.readLine()) != null) {
                String[] columns = line.split(",");
                for (int i = 0; i < 4; i++) {
                    if (result.size() <= i) {
                        result.add(new ArrayList<>());
                    }
                    result.get(i).add(Double.valueOf(columns[i]));
                }
            }
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }


    @Test
    public void TestReadFile() {
        String URL= "D:/example.txt";
        List<List<Double>> dataList = readTxtFile(URL);
        for(int i=0;i<1;i++){
            Double[] data = dataList.get(i).toArray(new Double[0]);
            System.out.println("数据偏度为：" + skewness(data));
            System.out.println("数据峰度为：" + kurtosis(data));
            System.out.println("数据平均值为：" + getAverage(data));
        }
    }

}
